package com.moduleSys.dao.base.impl;

import com.moduleSys.dao.base.BaseDao;
import com.moduleSys.entity.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <pre>
 *  @author caizw
 *  @createDate 2018/2/6
 *  @description
 *  <p>
 *     T -----> 实体类
 *     PK ----> 主键对应的java数据类型
 *  </p>
 *  <p>
 *     调用此类中的方法，传入HQL或SQL带有预编译占位符的一律使用 :value 不能使用 =? 否则执行报错
 *     正确：select * from users u WHERE u.name=:name and u.phone_number in(:phone_number)
 *     错误：select * from users u WHERE u.name=? and u.phone_number in(:phone_number)  <br/>
 *     in 查询括号中必须是 : 符号，不能是 ? 号，参数必须是list集合
 *  </p>
 * </pre>
 */
@Repository
public abstract class BaseDaoImpl<T, PK extends Serializable> implements BaseDao<T, PK> {

    private Logger logger = LoggerFactory.getLogger(BaseDaoImpl.class);
    private Class<T> clazz = getClazzGenricType();

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void save(T entity) {
        entityManager.persist(entity);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void delete(T entity) {
        entityManager.remove(entityManager.merge(entity));
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void deleteDatchByPk(Object[] entityIds) {
        for (Object pk : entityIds) {
            entityManager.remove(entityManager.getReference(clazz, pk));
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void update(T entity) {
        this.persistence(entity);
    }

    @Override
    public T findByid(PK pk) {
        return (T) entityManager.find(clazz, pk);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void saveOrUpdate(T entity) {
        this.persistence(entity);
    }
    @Override
    public List<T> selectByFiled(String filed, Object object) {
        String sql = "from " + clazz.getSimpleName() + " u WHERE u." + filed + "=?";
        Query query = entityManager.createQuery(sql);
        query.setParameter(1, object);
        List<T> list = query.getResultList();
        entityManager.close();
        return list;
    }

    @Override
    public List<T> getList() {
        String sql = "from " + clazz.getSimpleName();
        Query query = entityManager.createQuery(sql);
        List<T> list = query.getResultList();
        entityManager.close();
        return list;
    }

    @Override
    public List<T> findListBySqlMoreFiled(String sql, Map<String, ? extends Object> paramMap) {
        Query query = entityManager.createNativeQuery(sql, clazz);
        return hqlOrSqlFind(query, paramMap);
    }

    @Override
    public List<T> findListByHqlMoreFiled(String hql, Map<String, ? extends Object> paramMap) {
        Query query = entityManager.createQuery(hql);
        return hqlOrSqlFind(query, paramMap);
    }


    private List<T> hqlOrSqlFind(Query query, Map<String, ? extends Object> paramMap) {
        Set<String> set = null;
        set = paramMap.keySet();
        List<String> filedlist = new ArrayList<>(set);
        for (int i = 0; i < filedlist.size(); i++) {
            query.setParameter(filedlist.get(i).toString(), paramMap.get(filedlist.get(i)));
        }
        List<T> resultList = query.getResultList();
        entityManager.close();
        return resultList;
    }

    @Override
    public List<T> findByMoreFiled(Class<T> clazz, Map<String, ? extends Object> paramMap) {
        String sql = "from " + clazz.getSimpleName() + " u WHERE ";
        Set<String> set = null;
        set = paramMap.keySet();
        List<String> list = new ArrayList<>(set);
        List<Object> filedlist = new ArrayList<>();
        for (String filed : list) {
            sql += "u." + filed + "=:" + filed + " and ";
            filedlist.add(filed);
        }
        sql = sql.substring(0, sql.length() - 4);
        Query query = entityManager.createQuery(sql);
        for (int i = 0; i < filedlist.size(); i++) {
            query.setParameter(filedlist.get(i).toString(), paramMap.get(filedlist.get(i)));
        }
        List<T> listRe = query.getResultList();
        entityManager.close();
        return listRe;
    }

    @Override
    public PageInfo<T> findPageByMoreFiled(Map<String, ? extends Object> paramMap, int pageSize, int pageNumber, String orderBy) {
        String hql = "from " + clazz.getSimpleName() + " u WHERE 1=1 ";
        Set<String> set = null;
        set = paramMap.keySet();
        List<String> list = new ArrayList<>(set);
        List<String> filedlist = new ArrayList<>();
        for (String filed : list) {
            Object o = paramMap.get(filed);
            if (o instanceof List) {
                hql += " and u." + filed + " in(:" + filed + ")";
            } else {
                hql += " and u." + filed + "=:" + filed;
            }
            filedlist.add(filed);
        }
        if (!StringUtils.isEmpty(orderBy)) {
            hql = hql + " order by " + orderBy;
        }
        Query query = entityManager.createQuery(hql);
        for (int i = 0; i < filedlist.size(); i++) {
            query.setParameter(filedlist.get(i).toString(), paramMap.get(filedlist.get(i)));
        }

        long totalSize = getCountNumber(hql, filedlist, paramMap, true);
        query.setFirstResult((pageNumber - 1) * pageSize);
        query.setMaxResults(pageSize);
        List<T> listResult = query.getResultList();
        entityManager.close();
        PageInfo<T> pageInfo = new PageInfo<T>();
        pageInfo.setList(listResult);
        pageInfo.setPageNumber(pageNumber);
        pageInfo.setPageSize(pageSize);
        pageInfo.setTotal(totalSize);
        pageInfo.setTotalPage(getCountPage(totalSize, pageSize));
        return pageInfo;
    }

    @Override
    public PageInfo<T> findPageByHqlMoreFiled(String hql, Map<String, ? extends Object> paramMap, int pageSize, int pageNumber) {
        Query query = entityManager.createQuery(hql);
        return commonMoreFiled(hql, query, paramMap, pageSize, pageNumber, true);
    }

    @Override
    public PageInfo<T> findPageBySqlMoreFiled(String sql, Map<String, ? extends Object> paramMap, int pageSize, int pageNumber) {
        Query query = entityManager.createNativeQuery(sql, clazz);
        return commonMoreFiled(sql, query, paramMap, pageSize, pageNumber, false);
    }


    private PageInfo<T> commonMoreFiled(String findSQL, Query query, Map<String, ? extends Object> paramMap, int pageSize, int pageNumber, boolean isHQL) {
        PageInfo<T> pageInfo = new PageInfo<T>();
        Set<String> set = null;
        set = paramMap.keySet();
        List<String> filedlist = new ArrayList<>(set);
        for (int i = 0; i < filedlist.size(); i++) {
            String columnValue = filedlist.get(i).toString();
            query.setParameter(columnValue, paramMap.get(filedlist.get(i)));
        }

        long totalSize = getCountNumber(findSQL, filedlist, paramMap, isHQL);

        query.setFirstResult((pageNumber - 1) * pageSize);
        query.setMaxResults(pageSize);
        List<T> listResult = query.getResultList();
        entityManager.close();
        pageInfo.setList(listResult);
        pageInfo.setPageNumber(pageNumber);
        pageInfo.setPageSize(pageSize);
        pageInfo.setTotal(totalSize);
        pageInfo.setTotalPage(getCountPage(totalSize, pageSize));
        return pageInfo;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public Integer updateFiled(String hql, Map<String, ? extends Object> paramMap, boolean isHQL) {
        int resurlt = 0;
        Query query = null;
        if (!isHQL) {
            query = entityManager.createNativeQuery(hql, clazz);
        } else {
            query = entityManager.createQuery(hql);
        }
        Set<String> set = null;
        set = paramMap.keySet();
        List<String> filedlist = new ArrayList<>(set);
        for (int i = 0; i < filedlist.size(); i++) {
            query.setParameter(filedlist.get(i).toString(), paramMap.get(filedlist.get(i)));
        }
        resurlt = query.executeUpdate();
        entityManager.close();
        return resurlt;
    }

    /**
     * 持久化操作，判断更新还是保存
     * @param entity
     */
    private void persistence(T entity){
        PK pk = getPkValueByName(entity, getIdField(clazz).getName());
        if (StringUtils.isEmpty(pk)) {
            this.save(entity);
        } else {
            T t = this.findByid(pk);
            try {
                reflectClass1ToClass2(entity, t);
                entityManager.merge(t);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("---------------更新出错---------------");
            }
        }
    }

    /**
     * 计算总页数
     *
     * @param total
     * @param pageSize
     * @return
     */
    private long getCountPage(long total, long pageSize) {
        long countPage = 1;
        if (total % pageSize == 0) {
            countPage = total / pageSize;
        } else {
            countPage = (total / pageSize) + 1;
        }
        return countPage;
    }

    private long getCountNumber(String findSQL, List<String> filedlist, Map<String, ? extends Object> paramMap, boolean isHQL) {
        findSQL = "select count(id) as total " + findSQL.substring(findSQL.indexOf("from"), findSQL.length());
        Query query = null;
        if (isHQL) {
            query = entityManager.createQuery(findSQL);
        } else {
            query = entityManager.createNativeQuery(findSQL);
        }
        for (int i = 0; i < filedlist.size(); i++) {
            query.setParameter(filedlist.get(i).toString(), paramMap.get(filedlist.get(i)));
        }
        List<Object> resultList = query.getResultList();
        entityManager.close();
        return Long.parseLong(resultList.get(0).toString());
    }

    private Class<T> getClazzGenricType() {
        Class<T> entityClass = null;
        Type t = getClass().getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            Type[] p = ((ParameterizedType) t).getActualTypeArguments();
            entityClass = (Class<T>) p[0];
        }
        return entityClass;
    }

    /**
     * 获取实体类主键
     *
     * @param clazz
     * @return
     */
    private Field getIdField(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        Field item = null;
        for (Field field : fields) {
            //获取实体类中标识@Id的字段
            Id id = field.getAnnotation(Id.class);
            if (id != null) {
                field.setAccessible(true);
                item = field;
                break;
            }
        }
        if (item == null) {
            Class<?> superclass = clazz.getSuperclass();
            if (superclass != null) {
                item = getIdField(superclass);
            }
        }
        return item;
    }

    /**
     * 根据主键名称获取实体类主键属性值
     *
     * @param t
     * @param pkName
     * @return
     */
    private PK getPkValueByName(T t, String pkName) {
        try {
            String firstLetter = pkName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + pkName.substring(1);
            Method method = t.getClass().getMethod(getter, new Class[]{});
            PK value = (PK) method.invoke(t, new Object[]{});
            return value;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 通过反射将 class1不为空的值赋值给class2
     *
     * @param class1
     * @param class2
     * @throws Exception
     */
    private void reflectClass1ToClass2(Object class1, Object class2) throws Exception {
        Field[] field = class1.getClass().getDeclaredFields();
        for (int i = 0; i < field.length; i++) {
            String name = field[i].getName();
            if ("serialVersionUID".equals(name)) {
                continue;
            }
            name = name.substring(0, 1).toUpperCase() + name.substring(1);
            Method m1 = class1.getClass().getMethod("get" + name);
            Object value = m1.invoke(class1);
            if (value != null) {
                Field f = field[i];
                f.setAccessible(true);
                f.set(class2, value);
            }
        }
    }

    /**
     * 获取实体类 @Column 的其中一个属性名称，一般用于统计表记录只统计一个属性值
     *
     * @return
     */
    private String getColumnName(boolean isHQL) {
        Field[] fields = clazz.getDeclaredFields();
        String fieldNames = null;
        for (Field field : fields) {
            if (field.isAnnotationPresent(Column.class)) {
                /**
                 * 获取字段名
                 */
                if (isHQL) {
                    fieldNames = field.getName();
                } else {
                    Column declaredAnnotation = field.getDeclaredAnnotation(Column.class);
                    fieldNames = declaredAnnotation.name();
                }
                break;
            }
        }
        return fieldNames;
    }

    /**
     * 通过获取类上的Table注解获取表名称
     *
     * @return
     */
    private String getTableName(boolean isHQL) {
        Table annotation = clazz.getAnnotation(Table.class);
        if (!StringUtils.isEmpty(annotation) && !isHQL) {
            String name = annotation.name();
            if (StringUtils.isEmpty(name)) {
                return name;
            }
        }
        return clazz.getSimpleName();
    }

    public boolean isEmptyList(List list) {
        if (list == null || list.size() == 0) {
            return true;
        } else {
            return false;
        }
    }
}
